#!/usr/bin/env ruby
#
#Nethooks (http://code.google.com/p/nethooks/) is made available under the MIT License.
#
#Copyright (c) 2012 Roussel Geoffrey.
#
#Permission is hereby granted, free of charge, to any person obtaining a copy of 
#this software and associated documentation files (the "Software"), to deal 
#in the Software without restriction, including without limitation the rights 
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
#copies of the Software, and to permit persons to whom the Software is 
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in 
#all copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
#THE SOFTWARE.
#
# Nethooks user side GUI and listener.
# ___________________________
#
# Overview,
# This script manage:
#
#	+ The set of rules and config file.
#	+ The QT GUI desktop icon, windows/logic.
#	+ A thread that is listening for external wrapper input on TCP port 25252 
#		External C wrapper send us informations about processes
#		and we return the rule/user answer, the wrapper can then 
#		allow or refuse the C function call to the original process.
#		-> unmatched events(which has no rule) are enqueued until the user accept or refuse them.
#	+ A QT timer that will dequeue events-popups onscreen.
#
 
require 'Qt'
require 'socket'

puts "Nethooks client: Started."

# GUI definitions.
UIPath="/media/D/CODE/nethooks/"
if not File.exist? UIPath then
	UIPath="./"
end
require "#{UIPath}/UI/Popup.rb"
require "#{UIPath}/UI/Rules.rb"
require "#{UIPath}/UI/AddApp.rb"
require "#{UIPath}/UI/AddRule.rb"
require "#{UIPath}/UI/Menu.rb"

# Alpha version.
$VERSION = (0xC0FFEEBABE*4/1E11).to_i/10.0

# Set this to 1 for debug informations.
$DEBUG = 1

# Filtering function calls (cf menu: enable/disable).
$FILTERING = true

# Prevent infinite loops or other bad behaviors when programmaticaly generating QT events / side effects.
$CODE_WIDGET_CONTROL = false

##################################################
# RULES (.nethooks.conf)
# Format of the "R_" tables following:
# Let say we have 2 applications, "prog1" and "prog2",
# prog1 has 3 rules, and prog 2 has 2, here is what the tables could look like:
#	$R_APPNAME		= [			      "prog1", 															"prog2"							]
#	$R_ACTION		= [    [        "connect",                  "bind",  	"socket" 	],				[		"connect", 		"*" 		]		]
#	$R_TRUST		= [	 [                  "0",                      "0",         "1"  	],		    	[       	      1,   		 0 	]       ]
#	$R_IP			= [	 [ "192.168.1.2",   "192.168.1.10",         "/.*/"  		],			    [           "/.*/", 		"/.*/" 	]       ]
#	$R_PORT			= [	 [				  "80",                 "1458",         "/.*/" 	],				[           "/.*/", 		"/.*/" 	]       ]
#	$R_FAMILY		= [	 [       "AF_INET",             "AF_INET",        "/.*/"  	], 			    [    "AF_UNIX", 		"/.*/" 	]       ]
#	$R_PROTOCOL		= [	 [				"TCP",                 "TCP",           "/.*/" 	],				[           "/.*/", 		"/.*/" 	]       ]

$R_APPNAME = []			# 1D : applications' cmdlines as reported in /proc
$R_TRUST = []			# 2D : 1 to trust or 0 to block
$R_ACTION = []			# 2D : library call (connect, bind, ....)
$R_IP = []				# 2D : IP
$R_PORT = []			# 2D : port
$R_FAMILY = []			# 2D : family
$R_PROTOCOL = []		# 2D : protocol

$R_CONFIG_FILE = "~/.nethooks.conf"

$USE_ICON_TRAY = true # use system tray or a floating push button icon

# Load rules from config file
def loadRules()
	puts "Nethooks client: Loading rules."
	configFile = File.expand_path($R_CONFIG_FILE)
	if not File.exists?(configFile) then
		puts "Nethooks client: ERROR: rules file \"#{configFile}\" does not exist."
		return false
	end

	begin
		fcontent = File.open(configFile,"r") {|io| io.read}
	rescue
		puts "Nethooks client: ERROR: cannot read rules file \"#{configFile}\" -> #{$!}"
		return false
	end

	$R_APPNAME.clear
	$R_TRUST.clear
	$R_ACTION.clear
	$R_IP.clear
	$R_PORT.clear
	$R_FAMILY.clear
	$R_PROTOCOL.clear
	
	loadingAppName = ""
	appID = -1
	fcontent.each(){ |l|
		l = l.lstrip.rstrip
		if l =~ /^cmdline>/ then
			loadingAppName = l.gsub(/(.*)\"(.*)\"/, '\2')
			if $DEBUG == 1 then
				puts "Nethooks client: Loading app: \"#{loadingAppName}\""
			end
			appID += 1
							
			$R_APPNAME += [ loadingAppName ]

			$R_TRUST += [[]]
			$R_ACTION += [[]]
			$R_IP += [[]]
			$R_PORT += [[]]
			$R_FAMILY += [[]]
			$R_PROTOCOL += [[]]

		elsif  loadingAppName != "" then # rules
			r = l.split("\t")
			if(r.size < 7) or (r[6] != '.') then
				if $DEBUG == 1 then
					puts "Nethooks client: Skipping rule: #{l}"
				end
			end
			p r.inspect
			if	(r[0] == "0") or (r[0] == "1") then
				if $DEBUG == 1 then
					puts "Nethooks client: Loading rule: #{l}"
				end
				$R_TRUST[appID] += [r[0]]
				$R_ACTION[appID] += [r[1]]
				$R_IP[appID] += [r[2]]
				$R_PORT[appID] += [r[3]]
				$R_FAMILY[appID] += [r[4]]
				$R_PROTOCOL[appID] += [r[5]]

			end
		end
	}
	if $DEBUG == 1 then
		puts $R_APPNAME.inspect
		puts $R_TRUST.inspect
		puts $R_ACTION.inspect
		puts $R_IP.inspect
		puts $R_PORT.inspect
		puts $R_FAMILY.inspect
		puts $R_PROTOCOL.inspect
	end
	return true
end

# Write rules to config file
def saveRules()
	puts "Nethooks client: Saving rules."
	configFile = File.expand_path($R_CONFIG_FILE)

	begin
		f = File.open(configFile,"w")
		lines = "# This file is generated automaticly by Nethooks;\n" +
					"# if you want to modify this file manually, you should:\n" +
					"#	.Save the rules from Nethooks GUI.\n" +
					"#	.Re-open this file from you text editor and make manual changes.\n" +
					"#	.Reload rules from Nethooks GUI\n\n"
		0.upto($R_APPNAME.size - 1){ |appID|
			lines += "cmdline>\"#{$R_APPNAME[appID]}\"\n"
			0.upto($R_TRUST[appID].size - 1){ |i|
				rule = [
							$R_TRUST[appID][i],
							$R_ACTION[appID][i],
							$R_IP[appID][i],
							$R_PORT[appID][i],
							$R_FAMILY[appID][i],
							$R_PROTOCOL[appID][i]
						  ]
				lines += "\t#{rule[0]}\t#{rule[1]}\t#{rule[2]}\t#{rule[3]}\t#{rule[4]}\t#{rule[5]}\t.\n"
			}
		}
		f.write(lines)
		f.close
	rescue
		puts "Nethooks client: ERROR: cannot save rules file \"#{configFile}\" -> #{$!}"
		return false
	end
	return true
end

# Return true if testString match regexString
# regexString must be enclosed in '/' to be treated as regex.
def regexTestRule(regexString, testString)
	r = regexString.dup
	if	(r[0,1] == '/') and
		(r[r.size - 1,1] == '/') then
		r[0] = ""
		r[r.size - 1] = ""
		begin
			if (testString =~ /#{r}/) then
				return true
			end
		rescue
			puts "Nethooks client: ERROR: invalid regexp  \"#{r}\" -> #{$!}"
		end
	end
	return false
end

# Return [appID, ruleID] if a rule match or nil.
# strict == true treat regexp has real values(not interpreted, strict rule match).
# trust == -1 to ignore trust value check(rule testing): see testRuleMatching()
def hasRule(app = "/.*/", trust = "-1", action = "/.*/", ip = "/.*/", port = "/.*/", family = "/.*/", protocol = "/.*/", strict = false)
	0.upto($R_APPNAME.size() - 1){ |appID|
		if ( ($R_APPNAME[appID] == app) or ((regexTestRule($R_APPNAME[appID], app) == true) and (strict == false)) ) then
			# app found
			0.upto($R_TRUST[appID].size() - 1){ |i|
			
				ret = [false, false, false, false ] # trust, action, ip, port

				rule = [
							$R_TRUST[appID][i],
							$R_ACTION[appID][i],
							$R_IP[appID][i],
							$R_PORT[appID][i],
							$R_FAMILY[appID][i],
							$R_PROTOCOL[appID][i]
						  ]

				if(rule[0] == trust) or (trust == "-1") then ret[0] = true end
				if rule[1] == action then ret[1] = true end
				if rule[2] == ip then ret[2] = true end
				if rule[3] == port then ret[3] = true end
				if rule[4] == family then ret[4] = true end
				if rule[5] == protocol then ret[5] = true end

				# adjust strict
				if (strict == false) then
					if (regexTestRule(rule[1], action) == true) then ret[1] = true end
					if (regexTestRule(rule[2], ip) == true) then ret[2] = true end
					if (regexTestRule(rule[3], port) == true) then ret[3] = true end
					if (regexTestRule(rule[4], family) == true) then ret[4] = true end
					if (regexTestRule(rule[5], protocol) == true) then ret[5] = true end
				end

				if  (ret[0] == true) and 
					(ret[1] == true) and
					(ret[2] == true) and
					(ret[3] == true) and
					(ret[4] == true) and
					(ret[5] == true) then
					return [appID, i]
				end
			}
			if strict == true then
				break # strict == we can break, otherwise other potential appnames regexps might match.
			end
		end
	}
	return nil
end

# Return first matching IDs or nil
def testRuleMatching(app, action, ip, port, family, protocol, strict = false)
	ret = hasRule(app, trust = "-1", action, ip, port, family, protocol, strict) # trust to -1 when testing rule, that's what we are looking for.
	if ret != nil then
		appID, ruleID = ret[0], ret[1]
#		trusted = $R_TRUST[appID][ruleID] == "1" ? true : false
		return [ appID, ruleID ]
	end
	return nil  # no rule matching
end
 
 def addRule(app, trust, action , ip, port, family, protocol)
	appID = -1
	0.upto($R_APPNAME.size() - 1){ |i|
		if ($R_APPNAME[i] == app) then
			# app found
			appID = i
			break
		end
	}
	if (appID == -1) then
			appID = $R_APPNAME.size
			addApp(app)
	end
	$R_TRUST[appID] += [trust]
	$R_ACTION[appID] += [action]
	$R_IP[appID] += [ip]
	$R_PORT[appID] += [port]
	$R_FAMILY[appID] += [family]
	$R_PROTOCOL[appID] += [protocol]
end

def delRuleByID(appID, ruleID)
	if(appID < 0) or (appID >= $R_APPNAME.size) then
		return false # wrong ID
	end
	if(ruleID < 0) or (ruleID >= $R_TRUST[appID].size) then
		return false # wrong ID
	end
	$R_TRUST[appID].delete_at(ruleID)
	$R_ACTION[appID].delete_at(ruleID)
	$R_IP[appID].delete_at(ruleID)
	$R_PORT[appID].delete_at(ruleID)
	$R_FAMILY[appID].delete_at(ruleID)
	$R_PROTOCOL[appID].delete_at(ruleID)
	return true # rule removed
end

def delRule(app, trust, action, ip, port, family, protocol)
	ret = hasRule(app, trust, action, ip, port, strict = true)
	if ret != nil then
		appID, ruleID = ret[0], ret[1]	
		delRuleByID(appID, ruleID)
		return true # rule removed
	end
	return false # rule not found
end

def addApp(app)
	$R_APPNAME += [ app ]
	$R_TRUST += [[]]
	$R_ACTION += [[]]
	$R_IP += [[]]
	$R_PORT += [[]]
	$R_FAMILY += [[]]
	$R_PROTOCOL += [[]]
end

def delAppByID(appID)
	if(appID < 0) or (appID >= $R_APPNAME.size) then
		return false # wrong ID
	end
	$R_APPNAME.delete_at(appID)
	$R_TRUST.delete_at(appID)
	$R_ACTION.delete_at(appID)
	$R_IP.delete_at(appID)
	$R_PORT.delete_at(appID)
	$R_FAMILY.delete_at(appID)
	$R_PROTOCOL.delete_at(appID)
	return true
end

def delApp(app)
	appID = -1
	0.upto($R_APPNAME.size() - 1){ |i|
		if ($R_APPNAME[i] == app) then
			# app found
			appID = i
			break
		end
	}
	if(appID == -1) then
		return false
	end
	delAppByID(appID)	
	return true
end

##################################################
# WINDOW HELPER
# Override QT main window so we can easly track our window states.

# Sync window state(nil when closed).
$popup_window = nil
$rules_window = nil
$menu_window = nil
$addApp_window = nil
$addRule_window = nil
$about_window = nil

class MyMainWindow < Qt::MainWindow
	def initialize()
		super
	end
	def closeEvent(event)
		# Sync window state(nil when offscreen).
		case windowTitle()
			when "Popup";			if $DEBUG == 1 then
										puts "Nethooks client: Closing Popup window."
									end
									if $DEQUEUE_ANSWER	== nil then
										# assume blocking if popup is closed with the X cross
										userBlock()
									end
									# save position / window size is fixed
									$popup_window_geometry = geometry
									# clean
									$popup_window = nil
									$popup_ui = nil					
			when "Rules";	if $DEBUG == 1 then
										puts "Nethooks client: Closing Rules window."
									end
									$rules_window_geometry = geometry
									$rules_window = nil
									$rules_ui = nil
									if $addApp_window != nil then
										$addApp_window.close
									end
									if $addRule_window != nil then
										$addRule_window.close
									end

			when "AddApp",
					  "SetApp";
									if $DEBUG == 1 then
										puts "Nethooks client: Closing Add/Set-App window."
									end
									$addApp_window = nil
									$addApp_ui = nil
									if $rules_window != nil then $rules_window.setDisabled(false) end
									
			when "AddRule",
					  "SetRule";
									if $DEBUG == 1 then
										puts "Nethooks client: Closing Add/Set-Rule window."
									end
									$addRule_window = nil
									$addRule_ui = nil
									if $rules_window != nil then $rules_window.setDisabled(false) end

			when "Menu";	if $DEBUG == 1 then
										puts "Nethooks client: Closing Menu window."
									end
									$menu_window = nil
									$menu_ui = nil
			when "About";	if $DEBUG == 1 then
										puts "Nethooks client: Closing About window."
									end
									$about_window = nil
			else
				if $DEBUG == 1 then
					puts "Nethooks client: WARNING Closing #{self.inspect}."
				end											
		end
	end
end

def closeAllWindows()
	if $popup_window != nil then $popup_window.close() end
	if $rules_window != nil then $rules_window.close() end
	if $menu_window != nil then $menu_window.close() end
	if $addApp_window != nil then $addApp_window.close() end
	if $addRule_window != nil then $addRule_window.close() end
	if $about_window != nil then $about_window.close() end
end

##################################################
# POPUP
# Popup window will appear when an application request overriden syscalls.

def popupWindow()
	if $popup_window != nil then return end
	$popup_window = MyMainWindow.new()
	$popup_ui = Ui_Popup_MainWindow.new()
	$popup_ui.setupUi($popup_window) 
	$popup_window.setWindowTitle("Popup")
	$popup_window.setWindowFlags(Qt::MSWindowsFixedSizeDialogHint | Qt::WindowStaysOnTopHint)
	$popup_window.setFixedSize(Qt::Size.new(409, 419))
	if not defined? $popup_window_geometry then
		screenSize = Qt::Application::desktop().screenGeometry()
		$popup_window_geometry = Qt::Rect.new(screenSize.width - ($popup_window.size().width + 20),
																			screenSize.height - ($popup_window.size().height + 30),
																			0,0) # We only need position for this one...
	end
	$popup_window.setGeometry(Qt::Rect.new($popup_window_geometry.x,$popup_window_geometry.y,$popup_window.size().width,$popup_window.size().height))
	setIcon($popup_window, $FILTERING)
	$popup_window.show
	connectPopup()
end

def setPopupData(app = "application-cmdline", pid = "12345",
								parentApp = "application-parent", parentPid = "54321",
								action = "connect", protocol = "protocol", family = "family",
								ip = "127.0.0.1", port = "1337", dns = "localhost",
								message = "default message testing.\nwith carrier return.")
	$popup_ui.lineEdit.setText(app)
	$popup_ui.lineEdit_2.setText(pid)
	$popup_ui.lineEdit_7.setText(parentApp)
	$popup_ui.lineEdit_5.setText(parentPid)
	$popup_ui.lineEdit_3.setText(action)
	$popup_ui.lineEdit_8.setText(protocol)
	$popup_ui.lineEdit_10.setText(family)
	$popup_ui.lineEdit_4.setText(ip)
	$popup_ui.lineEdit_9.setText(port)
	$popup_ui.lineEdit_6.setText(dns)
	$popup_ui.textEdit.setText(message)
end

def recordRuleFromPopup(trust = "0")
	if ($popup_ui.checkBox_5.checkState == 2) then # remember ? then create rule
		app = $popup_ui.lineEdit.text
		action = $popup_ui.checkBox.checkState == 2 ? $popup_ui.lineEdit_3.text : "/.*/"
		ip = $popup_ui.checkBox_2.checkState == 2 ? $popup_ui.lineEdit_4.text : "/.*/"
		port = $popup_ui.checkBox_3.checkState == 2 ? $popup_ui.lineEdit_9.text : "/.*/"
		family = $popup_ui.checkBox_7.checkState == 2 ? $popup_ui.lineEdit_10.text : "/.*/"
		protocol = $popup_ui.checkBox_6.checkState == 2 ? $popup_ui.lineEdit_8.text : "/.*/"

		addRule(app, trust, action , ip, port, family, protocol)

		if($rules_window != nil) then
			appID = 0
			if($rules_ui.listWidget.currentItem() != nil) then 
				appID = $rules_ui.listWidget.currentRow()
			end
			fillRules(appID)
		end
	end
end

def userTrust()
	recordRuleFromPopup(trust = "1")
saveRules()
	$DEQUEUE_ANSWER = true
	if $popup_window != nil then
		$popup_window.close()
	end
end

def userBlock()
	recordRuleFromPopup(trust = "0")
	$DEQUEUE_ANSWER = false
	if $popup_window != nil then
		$popup_window.close()
	end
end

# Connect popup widgets.
def connectPopup()
		$popup_ui.checkBox.connect(SIGNAL('clicked(bool)')){ |b| 
			if $DEBUG == 1 then
				puts "Nethooks client: Popup Action: #{b}"
			end
			if(b == false) then $popup_ui.checkBox_4.setChecked(false) end # All should not be checked if one is unchecked
		}
		$popup_ui.checkBox_2.connect(SIGNAL('clicked(bool)')){ |b|
			if $DEBUG == 1 then
				puts "Nethooks client: Popup IP: #{b}" 
			end
			if(b == false) then $popup_ui.checkBox_4.setChecked(false) end # All should not be checked if one is unchecked
		}
		$popup_ui.checkBox_3.connect(SIGNAL('clicked(bool)')){ |b| 
			if $DEBUG == 1 then
				puts "Nethooks client: Popup Port: #{b}" 
			end
			if(b == false) then $popup_ui.checkBox_4.setChecked(false) end # All should not be checked if one is unchecked
		}
		$popup_ui.checkBox_6.connect(SIGNAL('clicked(bool)')){ |b| 
			if $DEBUG == 1 then
				puts "Nethooks client: Popup Protocol: #{b}"
			end
			if(b == false) then $popup_ui.checkBox_4.setChecked(false) end # All should not be checked if one is unchecked
		}		
		$popup_ui.checkBox_7.connect(SIGNAL('clicked(bool)')){ |b| 
			if $DEBUG == 1 then
				puts "Nethooks client: Popup Family: #{b}"
			end
			if(b == false) then $popup_ui.checkBox_4.setChecked(false) end # All should not be checked if one is unchecked
		}
		$popup_ui.checkBox_4.connect(SIGNAL('clicked(bool)')){ |b| 
			if $DEBUG == 1 then
				puts "Nethooks client: Popup All: #{b}"
			end
			$popup_ui.checkBox.setChecked(b)
			$popup_ui.checkBox_2.setChecked(b)
			$popup_ui.checkBox_3.setChecked(b)
			$popup_ui.checkBox_6.setChecked(b)
			$popup_ui.checkBox_7.setChecked(b)
		}
		$popup_ui.checkBox_5.connect(SIGNAL('clicked(bool)')){ |b|
			if $DEBUG == 1 then
				puts "Nethooks client: Popup Remember: #{b.inspect}"
			end
			if (b == true) then
				if ($popup_ui.checkBox.checkState != 2) and
					($popup_ui.checkBox_2.checkState != 2) and
					($popup_ui.checkBox_3.checkState != 2) and
					($popup_ui.checkBox_6.checkState != 2) and
					($popup_ui.checkBox_7.checkState != 2) then
					# if remember click but none selected, then autoselect all.
					$popup_ui.checkBox.setChecked(true)
					$popup_ui.checkBox_2.setChecked(true)
					$popup_ui.checkBox_3.setChecked(true)
					$popup_ui.checkBox_6.setChecked(true)
					$popup_ui.checkBox_7.setChecked(true)
					$popup_ui.checkBox_4.setChecked(true)
 				end
			end
		}
		$popup_ui.pushButton.connect(SIGNAL('clicked()')){
			if $DEBUG == 1 then
				puts "Nethooks client: Popup TRUST"
			end
			userTrust()
		}
		$popup_ui.pushButton_2.connect(SIGNAL('clicked()')){ 
			if $DEBUG == 1 then
				puts "Nethooks client: Popup BLOCK"
			end
			userBlock()
		}
end

##################################################
# RULES
# Rules window for easy configuration access.

def rulesWindow()
	if $rules_window != nil then return end
	$rules_window = MyMainWindow.new()
	$rules_ui = Ui_Rules_MainWindow.new()
	$rules_ui.setupUi($rules_window) 
	$rules_window.setWindowTitle("Rules")
	if not defined? $rules_window_geometry then
		screenSize = Qt::Application::desktop().screenGeometry()
		$rules_window_geometry = Qt::Rect.new(screenSize.width - ($rules_window.size().width + 100), 50,
																			$rules_window.size().width,$rules_window.size().height)
	end
	$rules_window.setGeometry($rules_window_geometry)
	
	$rules_ui.listWidget_2.verticalScrollBar.hide
	$rules_ui.listWidget_3.verticalScrollBar.hide
	$rules_ui.listWidget_4.verticalScrollBar.hide
	$rules_ui.listWidget_7.verticalScrollBar.hide

	listStyleSheet =	"QListView {"+
							"		background-color: rgb(70, 70, 70);"+
							"		color: rgb(255, 255, 255);"+
							" }"+
							" QListView::item:alternate {"+
							"		background-color: rgb(70, 70, 70);"+
							"		color: rgb(200, 200, 200);"+
							" }"+
							" QListView::item:selected {"+
							"		background-color: rgb(200, 200, 200);"+
							"		color: rgb(10, 10, 10);"+
							" }"+
							" QListView::item:selected:!active {"+
							"		background-color: rgb(200, 200, 200);"+
							"		color: rgb(10, 10, 10);"+

							" }"+
							" QListView::item:selected:active {"+
							"		background-color: rgb(200, 200, 200);"+
							"		color: rgb(10, 10, 10);"+
							" }"+
							" QListView::item:hover {"+
							"		background-color: rgb(100, 100, 200);"+
							" }"

	$rules_ui.listWidget.setStyleSheet(listStyleSheet)							
	$rules_ui.listWidget_5.setStyleSheet(listStyleSheet)
	$rules_ui.listWidget_2.setStyleSheet(listStyleSheet)
	$rules_ui.listWidget_3.setStyleSheet(listStyleSheet)
	$rules_ui.listWidget_4.setStyleSheet(listStyleSheet)
	$rules_ui.listWidget_7.setStyleSheet(listStyleSheet)
	$rules_ui.listWidget_6.setStyleSheet(listStyleSheet)

	setIcon($rules_window, $FILTERING)
	$rules_window.show
	connectRules()
	fillRules(0)
end

# Fill the rules window will current apps and rules
def fillRules(appID)
	# prevent infinite loop (when selecting item by code, QT firering the select event, loopback...)
	$CODE_WIDGET_CONTROL = true
	# Fill rules table
	$rules_ui.listWidget.clear
	$rules_ui.listWidget_5.clear
	$rules_ui.listWidget_2.clear
	$rules_ui.listWidget_3.clear
	$rules_ui.listWidget_4.clear
	$rules_ui.listWidget_7.clear
	$rules_ui.listWidget_6.clear

	0.upto($R_APPNAME.size - 1){ |i|
			n = $R_APPNAME[i]
			$rules_ui.listWidget.addItem(n)
			# Regexp GUI coloring
			if	(n[0,1] == '/') and (n[n.size - 1,1] == '/') then
				$rules_ui.listWidget.item(i).setForeground(Qt::Brush.new(Qt::blue))
			end
	}
	if($R_APPNAME.size > 0) then
			if (appID < 0) or (appID >= $R_APPNAME.size) then
				appID = 0
			end
	else
		return
	end

	$rules_ui.listWidget.setCurrentItem($rules_ui.listWidget.item(appID))

	0.upto($R_TRUST[appID].size - 1){ |i|
		$rules_ui.listWidget_5.addItem($R_TRUST[appID][i])	  
		$rules_ui.listWidget_2.addItem($R_ACTION[appID][i])
		$rules_ui.listWidget_3.addItem($R_IP[appID][i])
		$rules_ui.listWidget_4.addItem($R_PORT[appID][i])
		$rules_ui.listWidget_7.addItem($R_FAMILY[appID][i])
		$rules_ui.listWidget_6.addItem($R_PROTOCOL[appID][i])
	}
	0.upto($R_TRUST[appID].size - 1){ |i|
		if($R_TRUST[appID][i] == "0") then
			$rules_ui.listWidget_5.item(i).setForeground(Qt::Brush.new(Qt::red))
			$rules_ui.listWidget_2.item(i).setForeground(Qt::Brush.new(Qt::red))
			$rules_ui.listWidget_3.item(i).setForeground(Qt::Brush.new(Qt::red))
			$rules_ui.listWidget_4.item(i).setForeground(Qt::Brush.new(Qt::red))
			$rules_ui.listWidget_7.item(i).setForeground(Qt::Brush.new(Qt::red))
			$rules_ui.listWidget_6.item(i).setForeground(Qt::Brush.new(Qt::red))
		else
			$rules_ui.listWidget_5.item(i).setForeground(Qt::Brush.new(Qt::green))
			$rules_ui.listWidget_2.item(i).setForeground(Qt::Brush.new(Qt::green))
			$rules_ui.listWidget_3.item(i).setForeground(Qt::Brush.new(Qt::green))
			$rules_ui.listWidget_4.item(i).setForeground(Qt::Brush.new(Qt::green))
			$rules_ui.listWidget_7.item(i).setForeground(Qt::Brush.new(Qt::green))
			$rules_ui.listWidget_6.item(i).setForeground(Qt::Brush.new(Qt::green))
		end
		# Blue for regexps
		n = $R_ACTION[appID][i]
		if	(n[0,1] == '/') and (n[n.size - 1,1] == '/') then
				$rules_ui.listWidget_2.item(i).setForeground(Qt::Brush.new(Qt::blue))
		end
		n = $R_IP[appID][i]
		if	(n[0,1] == '/') and (n[n.size - 1,1] == '/') then
				$rules_ui.listWidget_3.item(i).setForeground(Qt::Brush.new(Qt::blue))
		end
		n = $R_PORT[appID][i]
		if	(n[0,1] == '/') and (n[n.size - 1,1] == '/') then
				$rules_ui.listWidget_4.item(i).setForeground(Qt::Brush.new(Qt::blue))
		end
		n = $R_FAMILY[appID][i]
		if	(n[0,1] == '/') and (n[n.size - 1,1] == '/') then
				$rules_ui.listWidget_7.item(i).setForeground(Qt::Brush.new(Qt::blue))
		end
		n = $R_PROTOCOL[appID][i]
		if	(n[0,1] == '/') and (n[n.size - 1,1] == '/') then
				$rules_ui.listWidget_6.item(i).setForeground(Qt::Brush.new(Qt::blue))
		end
	}
	
	selectRule(appID, 0)
	$CODE_WIDGET_CONTROL = false
end

# Select and sync when the user select a rule
def selectRule(appID, ruleID = 0, skipSelect = nil)
	if(appID < 0) or (appID >= $R_APPNAME.size) then
		return
	end
	if($R_TRUST[appID].size == 0) then
		return
	end
	if(ruleID < 0) or (ruleID >= $R_TRUST[appID].size) then
		return
	end
	if skipSelect != "listWidget_5" then
		$rules_ui.listWidget_5.setCurrentItem($rules_ui.listWidget_5.item(ruleID))
	end
	if skipSelect != "listWidget_2" then 
		$rules_ui.listWidget_2.setCurrentItem($rules_ui.listWidget_2.item(ruleID))
	end
	if skipSelect != "listWidget_3" then
		$rules_ui.listWidget_3.setCurrentItem($rules_ui.listWidget_3.item(ruleID))
	end
	if skipSelect != "listWidget_4" then
		$rules_ui.listWidget_4.setCurrentItem($rules_ui.listWidget_4.item(ruleID))
	end
	if skipSelect != "listWidget_7" then 
		$rules_ui.listWidget_7.setCurrentItem($rules_ui.listWidget_7.item(ruleID))
	end
	if skipSelect != "listWidget_6" then 
		$rules_ui.listWidget_6.setCurrentItem($rules_ui.listWidget_6.item(ruleID))
	end
end

# Callbacks needed in multiple places(button + item double click events), see connectRules()
def _editApp()
	if $DEBUG == 1 then
		puts "Nethooks client: Rules Edit App" 
	end
	appID = $rules_ui.listWidget.currentRow()
	addOrEditAppWindow(appID)
end
def _editRule()
			if $DEBUG == 1 then
				puts "Nethooks client: Rules Edit rule" 
			end
			if($R_APPNAME.size == 0) then
				return
			end
			appID = $rules_ui.listWidget.currentRow()
			if ($R_TRUST[appID].size == 0) then
				return
			end
			trust = $rules_ui.listWidget_5.currentItem().text()
			action = $rules_ui.listWidget_2.currentItem().text()
			ip = $rules_ui.listWidget_3.currentItem().text()
			port = $rules_ui.listWidget_4.currentItem().text()
			family = $rules_ui.listWidget_7.currentItem().text()
			protocol = $rules_ui.listWidget_6.currentItem().text()
			addOrEditRuleWindow(ruleID = $rules_ui.listWidget_5.currentRow(), trust,action,ip,port,family,protocol)
end
def _appUpDown(appID, up = true)
	move = -1 # up
	if up == true then
		if appID == 0 then
			return false
		end
	end
	if up == false then
		if appID ==  ($R_APPNAME.size - 1) then 
			return false
		end
		move = 1
	end
	
	app = $R_APPNAME[appID]
	trust = $R_TRUST[appID]
	action = $R_ACTION[appID]
	ip = $R_IP[appID]
	port = $R_PORT[appID]
	family = $R_FAMILY[appID]
	protocol = $R_PROTOCOL[appID]

	moveID = appID + move
	
	$R_APPNAME[appID] = $R_APPNAME[moveID]
	$R_TRUST[appID] = $R_TRUST[moveID]
	$R_ACTION[appID] = $R_ACTION[moveID]
	$R_IP[appID] = $R_IP[moveID]
	$R_PORT[appID] = $R_PORT[moveID]
	$R_FAMILY[appID] = $R_FAMILY[moveID]
	$R_PROTOCOL[appID] = $R_PROTOCOL[moveID]

	$R_APPNAME[moveID] = app
	$R_TRUST[moveID] = trust
	$R_ACTION[moveID] = action
	$R_IP[moveID] = ip
	$R_PORT[moveID] = port
	$R_FAMILY[moveID] = family
	$R_PROTOCOL[moveID] = protocol
	
	return true
end
def _ruleUpDown(appID, ruleID, up = true)
	move = -1 # up
	if up == true then
		if ruleID == 0 then
			return false
		end
	end
	if up == false then
		if ruleID == ($R_TRUST[appID].size - 1) then
			return false
		end
		move = 1
	end
	
	trust = $R_TRUST[appID][ruleID]
	action = $R_ACTION[appID][ruleID]
	ip = $R_IP[appID][ruleID]
	port = $R_PORT[appID][ruleID]
	family = $R_FAMILY[appID][ruleID]
	protocol = $R_PROTOCOL[appID][ruleID]

	moveID = ruleID + move
	
	$R_TRUST[appID][ruleID] = $R_TRUST[appID][moveID]
	$R_ACTION[appID][ruleID] = $R_ACTION[appID][moveID]
	$R_IP[appID][ruleID] = $R_IP[appID][moveID]
	$R_PORT[appID][ruleID] = $R_PORT[appID][moveID]
	$R_FAMILY[appID][ruleID] = $R_FAMILY[appID][moveID]
	$R_PROTOCOL[appID][ruleID] = $R_PROTOCOL[appID][moveID]

	$R_TRUST[appID][moveID] = trust
	$R_ACTION[appID][moveID] = action
	$R_IP[appID][moveID] = ip
	$R_PORT[appID][moveID] = port
	$R_FAMILY[appID][moveID] = family
	$R_PROTOCOL[appID][moveID] = protocol
	
	return  true
end
# trust, action, ip, port, family, protocol
$COPY_PASTE_RULE = [nil, nil, nil, nil, nil, nil]
def _copyRule()
	if $DEBUG == 1 then
		puts "Nethooks client: Rules Copy rule"
	end
	if($rules_ui.listWidget.currentItem() == nil) or
		($rules_ui.listWidget_5.currentItem() == nil)
	then
		return false
	end
	appID = $rules_ui.listWidget.currentRow()
	ruleID = $rules_ui.listWidget_5.currentRow()
	$COPY_PASTE_RULE[0] = $R_TRUST[appID][ruleID]
	$COPY_PASTE_RULE[1] = $R_ACTION[appID][ruleID]
	$COPY_PASTE_RULE[2] = $R_IP[appID][ruleID]
	$COPY_PASTE_RULE[3] = $R_PORT[appID][ruleID]
	$COPY_PASTE_RULE[4] = $R_FAMILY[appID][ruleID]
	$COPY_PASTE_RULE[5] = $R_PROTOCOL[appID][ruleID]
	return true
end
def _pasteRule()
	if $DEBUG == 1 then
		puts "Nethooks client: Rules Paste rule"
	end
	if($COPY_PASTE_RULE[0] == nil) then
		return false
	end
	if($rules_ui.listWidget.currentItem() == nil) then
		return false
	end
	addRule($rules_ui.listWidget.currentItem().text(),
		$COPY_PASTE_RULE[0],
		$COPY_PASTE_RULE[1],
		$COPY_PASTE_RULE[2],
		$COPY_PASTE_RULE[3],
		$COPY_PASTE_RULE[4],
		$COPY_PASTE_RULE[5])
	return true
end
def _redrawRules(ruleID = nil)
	if ruleID == nil then
		ruleID = $rules_ui.listWidget_5.currentRow()
	end
	fillRules($rules_ui.listWidget.currentRow())
	selectRule($rules_ui.listWidget.currentRow(), ruleID)
end

# Connect rules widgets.
def connectRules()
		$rules_ui.listWidget.connect(SIGNAL('currentRowChanged(int)')){
			if($rules_ui.listWidget.currentItem() != nil) and ($CODE_WIDGET_CONTROL == false) then 
				if $DEBUG == 1 then
					puts "Nethooks client: Rules App Select \"#{$rules_ui.listWidget.currentItem().text()}\""
				end
				fillRules($rules_ui.listWidget.currentRow())
			end
		}
		$rules_ui.listWidget.connect(SIGNAL('itemDoubleClicked(QListWidgetItem*)')){ _editApp() }
		
		$rules_ui.listWidget_5.connect(SIGNAL('currentRowChanged(int)')){
			if($rules_ui.listWidget_5.currentItem() != nil) and ($CODE_WIDGET_CONTROL == false) then 
				if $DEBUG == 1 then
					puts "Nethooks client: Rules Trust Select \"#{$rules_ui.listWidget_5.currentItem().text()}\""
				end
				$CODE_WIDGET_CONTROL = true
				selectRule(appID = $rules_ui.listWidget.currentRow(), ruleID = $rules_ui.listWidget_5.currentRow(), "listWidget_5")
				$CODE_WIDGET_CONTROL = false
			end
		}
		$rules_ui.listWidget_5.verticalScrollBar.connect(SIGNAL('valueChanged(int)')){ |v|
			$rules_ui.listWidget_2.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_3.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_4.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_7.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_6.verticalScrollBar.setValue(v)
		}
		$rules_ui.listWidget_5.connect(SIGNAL('itemDoubleClicked(QListWidgetItem*)')){ _editRule() }

		$rules_ui.listWidget_2.connect(SIGNAL('currentRowChanged(int)')){
			if($rules_ui.listWidget_2.currentItem() != nil) and ($CODE_WIDGET_CONTROL == false) then 
				if $DEBUG == 1 then
					puts "Nethooks client: Rules Action Select \"#{$rules_ui.listWidget_2.currentItem().text()}\""
				end
				$CODE_WIDGET_CONTROL = true
				selectRule(appID = $rules_ui.listWidget.currentRow(), ruleID = $rules_ui.listWidget_2.currentRow(), "listWidget_2")
				$CODE_WIDGET_CONTROL = false
			end
		}
		$rules_ui.listWidget_2.verticalScrollBar.connect(SIGNAL('valueChanged(int)')){ |v|
			$rules_ui.listWidget_5.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_3.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_4.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_7.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_6.verticalScrollBar.setValue(v)
		}
		$rules_ui.listWidget_2.connect(SIGNAL('itemDoubleClicked(QListWidgetItem*)')){ _editRule() }

		$rules_ui.listWidget_3.connect(SIGNAL('currentRowChanged(int)')){
			if($rules_ui.listWidget_3.currentItem() != nil) and ($CODE_WIDGET_CONTROL == false) then 
				if $DEBUG == 1 then
					puts "Nethooks client: Rules IP Select \"#{$rules_ui.listWidget_3.currentItem().text()}\""
				end
				$CODE_WIDGET_CONTROL = true
				selectRule(appID = $rules_ui.listWidget.currentRow(), ruleID = $rules_ui.listWidget_3.currentRow(),  "listWidget_3")
				$CODE_WIDGET_CONTROL = false
			end
		}
		$rules_ui.listWidget_3.verticalScrollBar.connect(SIGNAL('valueChanged(int)')){ |v|
			$rules_ui.listWidget_5.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_2.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_4.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_7.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_6.verticalScrollBar.setValue(v)
		}
		$rules_ui.listWidget_3.connect(SIGNAL('itemDoubleClicked(QListWidgetItem*)')){ _editRule() }

		$rules_ui.listWidget_4.connect(SIGNAL('currentRowChanged(int)')){
			if($rules_ui.listWidget_4.currentItem() != nil) and ($CODE_WIDGET_CONTROL == false) then 
				if $DEBUG == 1 then
					puts "Nethooks client: Rules Port Select \"#{$rules_ui.listWidget_4.currentItem().text()}\""
				end
				$CODE_WIDGET_CONTROL = true
				selectRule(appID = $rules_ui.listWidget.currentRow(), ruleID = $rules_ui.listWidget_4.currentRow(), "listWidget_4")
				$CODE_WIDGET_CONTROL = false
			end
		}
		$rules_ui.listWidget_4.verticalScrollBar.connect(SIGNAL('valueChanged(int)')){ |v|
			$rules_ui.listWidget_5.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_2.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_3.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_7.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_6.verticalScrollBar.setValue(v)
		}		
		$rules_ui.listWidget_4.connect(SIGNAL('itemDoubleClicked(QListWidgetItem*)')){ _editRule() }

		$rules_ui.listWidget_7.connect(SIGNAL('currentRowChanged(int)')){
			if($rules_ui.listWidget_7.currentItem() != nil) and ($CODE_WIDGET_CONTROL == false) then 
				if $DEBUG == 1 then
					puts "Nethooks client: Rule Family Select \"#{$rules_ui.listWidget_7.currentItem().text()}\""
				end
				$CODE_WIDGET_CONTROL = true
				selectRule(appID = $rules_ui.listWidget.currentRow(), ruleID = $rules_ui.listWidget_7.currentRow(), "listWidget_7")
				$CODE_WIDGET_CONTROL = false
			end
		}
		$rules_ui.listWidget_7.verticalScrollBar.connect(SIGNAL('valueChanged(int)')){ |v|
			$rules_ui.listWidget_5.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_2.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_3.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_4.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_6.verticalScrollBar.setValue(v)
		}
		$rules_ui.listWidget_7.connect(SIGNAL('itemDoubleClicked(QListWidgetItem*)')){ _editRule() }

		$rules_ui.listWidget_6.connect(SIGNAL('currentRowChanged(int)')){
			if($rules_ui.listWidget_6.currentItem() != nil) and ($CODE_WIDGET_CONTROL == false) then 
				if $DEBUG == 1 then
					puts "Nethooks client: Rule Protocol Select \"#{$rules_ui.listWidget_6.currentItem().text()}\""
				end
				$CODE_WIDGET_CONTROL = true
				selectRule(appID = $rules_ui.listWidget.currentRow(), ruleID = $rules_ui.listWidget_6.currentRow(), "listWidget_6")
				$CODE_WIDGET_CONTROL = false
			end
		}
		$rules_ui.listWidget_6.verticalScrollBar.connect(SIGNAL('valueChanged(int)')){ |v|
			$rules_ui.listWidget_5.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_2.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_3.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_4.verticalScrollBar.setValue(v)
			$rules_ui.listWidget_7.verticalScrollBar.setValue(v)
		}
		$rules_ui.listWidget_6.connect(SIGNAL('itemDoubleClicked(QListWidgetItem*)')){ _editRule() }

		$rules_ui.pushButton_3.connect(SIGNAL('clicked()')){ 
			if $DEBUG == 1 then
				puts "Nethooks client: Rules Add app"
				addOrEditAppWindow(-1)
			end
		}
		$rules_ui.pushButton_4.connect(SIGNAL('clicked()')){
			if $DEBUG == 1 then
				puts "Nethooks client: Rules Del app"
			end
			if($rules_ui.listWidget.currentItem() != nil)
				delAppByID($rules_ui.listWidget.currentRow())
				fillRules(-1)
			end
		}
		$rules_ui.pushButton_7.connect(SIGNAL('clicked()')){
			if $DEBUG == 1 then
				puts "Nethooks client: Rules Up app"
			end
			if($rules_ui.listWidget.currentItem() != nil)
				ret = _appUpDown($rules_ui.listWidget.currentRow(), up = true)
				if(ret == true) then
					appID =$rules_ui.listWidget.currentRow() - 1
					$rules_ui.listWidget.setCurrentItem($rules_ui.listWidget.item(appID))
				end
			end
		}
		$rules_ui.pushButton_8.connect(SIGNAL('clicked()')){
			if $DEBUG == 1 then
				puts "Nethooks client: Rules Down app"
			end
			if($rules_ui.listWidget.currentItem() != nil)
				ret = _appUpDown($rules_ui.listWidget.currentRow(), up = false)
				if(ret == true) then
					appID =$rules_ui.listWidget.currentRow() + 1
					$rules_ui.listWidget.setCurrentItem($rules_ui.listWidget.item(appID))
				end
			end
		}

		$rules_ui.pushButton_6.connect(SIGNAL('clicked()')){ 
			if $DEBUG == 1 then
				puts "Nethooks client: Rules Update rules" 
			end
			if($rules_ui.listWidget.currentItem() != nil) and ($CODE_WIDGET_CONTROL == false) then 
				fillRules($rules_ui.listWidget.currentRow())
			end
		}
		$rules_ui.pushButton_5.connect(SIGNAL('clicked()')){ 
			_editRule()
		}
		$rules_ui.pushButton.connect(SIGNAL('clicked()')){ 
			if $DEBUG == 1 then
				puts "Nethooks client: Rules Add rule"
			end
			if($R_APPNAME.size == 0) then
				next
			end
			addOrEditRuleWindow(-1, trust = 0, action = "action", ip = "IP", port = "port", family = "family", protocol = "protocol")
		}
		$rules_ui.pushButton_2.connect(SIGNAL('clicked()')){ 
			if $DEBUG == 1 then
				puts "Nethooks client: Rules Del rule"
			end
			if($rules_ui.listWidget.currentItem() != nil) then
				if($rules_ui.listWidget_5.currentItem() != nil)
					delRuleByID($rules_ui.listWidget.currentRow(), $rules_ui.listWidget_5.currentRow())
					fillRules($rules_ui.listWidget.currentRow())
				end
			end
		}

		$rules_ui.pushButton_9.connect(SIGNAL('clicked()')){
			if $DEBUG == 1 then
				puts "Nethooks client: Rules Up rule"
			end
			if(($rules_ui.listWidget.currentItem() != nil) and ($rules_ui.listWidget_5.currentItem() != nil)) then
				ret = _ruleUpDown($rules_ui.listWidget.currentRow(), $rules_ui.listWidget_5.currentRow(), up = true)
				if(ret == true) then
					_redrawRules($rules_ui.listWidget_5.currentRow() - 1)
				end
			end
		}
		$rules_ui.pushButton_10.connect(SIGNAL('clicked()')){
			if $DEBUG == 1 then
				puts "Nethooks client: Rules Down rule"
			end
			if(($rules_ui.listWidget.currentItem() != nil) and ($rules_ui.listWidget_5.currentItem() != nil)) then
				ret = _ruleUpDown($rules_ui.listWidget.currentRow(), $rules_ui.listWidget_5.currentRow(), up = false)
				if(ret == true) then
					_redrawRules($rules_ui.listWidget_5.currentRow() + 1)
				end
			end
		}
		$rules_ui.pushButton_11.connect(SIGNAL('clicked()')){
			ret = _copyRule()
			if(ret == true) then
				_redrawRules($rules_ui.listWidget_5.currentRow())
			end
		}
		$rules_ui.pushButton_12.connect(SIGNAL('clicked()')){
			ret = _pasteRule()
			if(ret == true) then
				_redrawRules()
				appID = $rules_ui.listWidget.currentRow()
				_redrawRules($R_TRUST[appID].size - 1)
			end
		}
end

##################################################
# ADD APP WINDOW
# AppID set to -1 add a new app, set it to a valid ID to turn into edit mode
$addOrEdit_appID = -1
def addOrEditAppWindow(appID = -1)
	if $addApp_window != nil then return end
	$rules_window.setDisabled(true) # disable rule window until this one get closed
	$addApp_window = MyMainWindow.new()
	$addApp_ui = Ui_AddApp_MainWindow.new()
	$addApp_ui.setupUi($addApp_window) 
	$addApp_window.setWindowTitle("AddApp")
	$addApp_window.setWindowFlags(Qt::WindowStaysOnTopHint)
	$addApp_window.setFixedSize(Qt::Size.new(352, 89))

	xpos = Qt::Cursor::pos().x - $addApp_window.geometry.width * 0.5
 	ypos = Qt::Cursor::pos().y - $addApp_window.geometry.height * 0.5
	$addApp_window.setGeometry(Qt::Rect.new(xpos,ypos,$addApp_window.size().width,$addApp_window.size().height))
	setIcon($addApp_window, $FILTERING)

	$addOrEdit_appID =  appID
	if $addOrEdit_appID != -1 then # edit mode
		$addApp_window.setWindowTitle("SetApp")
		$addApp_ui.pushButton.setText("Set app")
		$addApp_ui.lineEdit.setText($rules_ui.listWidget.currentItem().text())
	end	
	
	$addApp_window.show
	connectAddOrEditApp()
end

# Connect addApp widgets.
def connectAddOrEditApp()
		$addApp_ui.pushButton.connect(SIGNAL('clicked()')){ 
			if $DEBUG == 1 then
				puts "Nethooks client: AddApp: Add app" 
			end
			app = $addApp_ui.lineEdit.text()
			# TODO if regex test regex validity
			validApp = true
			if(app == "") then validApp = false end	
			if validApp == false then
				# TODO show error
				next
			end
			if $addOrEdit_appID == -1 then # add rule
				addApp(app)
				$addOrEdit_appID = $R_APPNAME.size - 1
			else
				$R_APPNAME[$addOrEdit_appID] = app
			end
			fillRules($addOrEdit_appID)

			# select app
			$CODE_WIDGET_CONTROL = true
			$rules_ui.listWidget.setCurrentItem($rules_ui.listWidget.item($addOrEdit_appID))
			$CODE_WIDGET_CONTROL = false

			$addApp_window.close
		}
		$addApp_ui.pushButton_2.connect(SIGNAL('clicked()')){ 
			if $DEBUG == 1 then
				puts "Nethooks client: AddApp: Cancel" 
			end
			rulesWindow()
			$addApp_window.close
		}
end

##################################################
# ADD RULE WINDOW

# RuleID set to -1 add a new rule, set it to a valid ID to turn into edit mode
$addOrEdit_ruleID = -1
def addOrEditRuleWindow(ruleID = -1, trust = 0, action = "action", ip = "IP", port = "port", family = "family", protocol = "protocol")
	if $addRule_window != nil then return end
	$rules_window.setDisabled(true) # disable rule window until this one get closed
	$addRule_window = MyMainWindow.new()
	$addRule_ui = Ui_AddRule_MainWindow.new()
	$addRule_ui.setupUi($addRule_window) 
	$addRule_window.setWindowTitle("AddRule")
	$addRule_window.setWindowFlags(Qt::WindowStaysOnTopHint)
	$addRule_window.setFixedSize(Qt::Size.new(662, 128))

	xpos = Qt::Cursor::pos().x - $addRule_window.geometry.width * 0.5
 	ypos = Qt::Cursor::pos().y - $addRule_window.geometry.height * 0.5
	$addRule_window.setGeometry(Qt::Rect.new(xpos,ypos,$addRule_window.size().width,$addRule_window.size().height))
	setIcon($addRule_window, $FILTERING)

	$addOrEdit_ruleID =  ruleID
	if $addOrEdit_ruleID != -1 then # edit mode
		$addRule_window.setWindowTitle("SetRule")
		$addRule_ui.pushButton.setText("Set rule")
	end
	
	$addRule_ui.listWidget.setCurrentItem($addRule_ui.listWidget.item(trust.to_i))
	$addRule_ui.listWidget.item(trust.to_i).setSelected(true)
	$addRule_ui.lineEdit.setText(action)
	$addRule_ui.lineEdit_2.setText(ip)
	$addRule_ui.lineEdit_3.setText(port)
	$addRule_ui.lineEdit_4.setText(family)
	$addRule_ui.lineEdit_5.setText(protocol)

	$addRule_window.show
	connectAddorEditRule()
end

# Connect addRule widgets.
def connectAddorEditRule()
		$addRule_ui.pushButton.connect(SIGNAL('clicked()')){ 
			if $DEBUG == 1 then
				puts "Nethooks client: AddRule: Add rule" 
			end
			if($R_APPNAME.size == 0) then
				next
			end
			trust = $addRule_ui.listWidget.currentRow.to_s
			action = $addRule_ui.lineEdit.text
			ip = $addRule_ui.lineEdit_2.text
			port = $addRule_ui.lineEdit_3.text
			family = $addRule_ui.lineEdit_4.text
			protocol = $addRule_ui.lineEdit_5.text
			# TODO test all fields
			validRule = true
			if validRule == false then
				# TODO show error
				next
			end
			appID = $rules_ui.listWidget.currentRow
			if $addOrEdit_ruleID == -1 then # add rule
				addRule($R_APPNAME[appID], trust, action , ip, port, family, protocol)
				$addOrEdit_ruleID = $R_TRUST[appID].size - 1
			else # edit rule
				puts "Editing rule for appID(#{appID}) ruleID(#{$addOrEdit_ruleID})"
				$R_TRUST[appID][$addOrEdit_ruleID] = trust
				$R_ACTION[appID][$addOrEdit_ruleID] = action
				$R_IP[appID][$addOrEdit_ruleID] = ip
				$R_PORT[appID][$addOrEdit_ruleID] = port
				$R_FAMILY[appID][$addOrEdit_ruleID] = family
				$R_PROTOCOL[appID][$addOrEdit_ruleID] = protocol
			end
			fillRules(appID)

			# select rule
			$CODE_WIDGET_CONTROL = true
			selectRule(appID, $addOrEdit_ruleID, skipSelect = nil)
			$CODE_WIDGET_CONTROL = false

			$addRule_window.close
		}
		$addRule_ui.pushButton_2.connect(SIGNAL('clicked()')){ 
			if $DEBUG == 1 then
				puts "Nethooks client: AddRule: Add Rules" 
			end
			$addRule_window.close
		}
end

##################################################
# MENU

# Menu window when clicking icon.
def menuWindow()
	if $menu_window != nil then return end
	$menu_window = MyMainWindow.new()
	$menu_ui = Ui_Menu_MainWindow.new()
	$menu_ui.setupUi($menu_window) 
	$menu_window.setWindowTitle("Menu")
	$menu_window.setWindowFlags(Qt::WindowStaysOnTopHint)
	$menu_window.setFixedSize(Qt::Size.new(95, 190))
	if $FILTERING == false then
		$menu_ui.pushButton_3.setText("Enable")
	end
	screenSize = Qt::Application::desktop().screenGeometry()
	xpos, ypos = $buttonIcon.geometry.x, $buttonIcon.geometry.y
	xpos = xpos <= screenSize.width - 200 ? xpos += 40 : xpos -= 100
	ypos = ypos >= screenSize.height - 200 ? ypos -= 200 : ypos += 20
	$menu_window.setGeometry(Qt::Rect.new(xpos,ypos,$menu_window.size().width,$menu_window.size().height))
	setIcon($menu_window, $FILTERING)
	$menu_window.show
	connectMenu()
end

# Connect menu widgets.
def connectMenu()
		$menu_ui.pushButton_3.connect(SIGNAL('clicked()')){ 
			if $DEBUG == 1 then
				puts "Nethooks client: Menu: Disable" 
			end
			$FILTERING = !$FILTERING
			setIcon($buttonIcon, $FILTERING)
			if $popup_window != nil then setIcon($popup_window, $FILTERING) end
			if $rules_window != nil then setIcon($rules_window, $FILTERING) end
			if $menu_window != nil then setIcon($menu_window, $FILTERING) end
			$menu_window.close
		}
		$menu_ui.pushButton.connect(SIGNAL('clicked()')){ 
			if $DEBUG == 1 then
				puts "Nethooks client: Menu: Rules" 
			end
			if $rules_window != nil then
				$rules_window.close
			end
			rulesWindow()
			$menu_window.close
		}
		$menu_ui.pushButton_4.connect(SIGNAL('clicked()')){ 
			if $DEBUG == 1 then
				puts "Nethooks client: Menu: Reload"
			end
			loadRules()
			if($rules_window != nil) then
				appID = -1
				if($rules_ui.listWidget.currentItem() != nil) then 
					appID = $rules_ui.listWidget.currentRow()
				end
				fillRules(appID)
			end
			$menu_window.close
		}
		$menu_ui.pushButton_5.connect(SIGNAL('clicked()')){ 
			if $DEBUG == 1 then
				puts "Nethooks client: Menu: Save"
			end
			ret = saveRules()
			# TODO show a popup with save ret result.
			$menu_window.close
		}
		$menu_ui.pushButton_6.connect(SIGNAL('clicked()')){ 
			if $DEBUG == 1 then
				puts "Nethooks client: Menu: About"
			end
			about()
			$menu_window.close
		}
		$menu_ui.pushButton_2.connect(SIGNAL('clicked()')){
			if $DEBUG == 1 then
				puts "Nethooks client: Menu: Exit"
			end
			closeAllWindows()
			if $buttonIcon.class != Qt::SystemTrayIcon then
				$buttonIcon.close
			end
			exit 0
		}
end

##################################################
# ABOUT

def about()
	if $about_window != nil then
		return
	end
	$about_window = MyMainWindow.new()
	$about_window.setWindowFlags((Qt::MSWindowsFixedSizeDialogHint | Qt::WindowStaysOnTopHint))
	$about_window.setWindowTitle("About")
	xpos,ypos = Qt::Cursor::pos().x,Qt::Cursor::pos().y
	$about_window.setGeometry(Qt::Rect.new(xpos, ypos, 100, 100))	
	
	label = Qt::Label.new(
'
<P ALIGN=CENTER STYLE="margin-bottom: 0cm">
<FONT SIZE=4 STYLE="font-size: 15pt">
<U><B>Nethooks '+ $VERSION.to_s + '</B></U></FONT></P> 
<P ALIGN=CENTER STYLE="margin-bottom: 0cm">
<I>Userspace application-firewall.</I></P> 
<P ALIGN=CENTER STYLE="margin-bottom: 0cm"> 
</P>
<HR>
<CENTER><B>Nethooks is made available under the MIT License.</B></CENTER>
<PRE CLASS="western" STYLE="margin-bottom: 0.5cm; text-align: center"></PRE> 
<P ALIGN=CENTER STYLE="margin-bottom: 0cm">
<P ALIGN=CENTER STYLE="margin-bottom: 0cm">Copyright &copy; 2012
<SPAN STYLE="font-weight: normal">Roussel Geoffrey</SPAN> -
<A HREF="mailto:roussel.geoffrey@gmail.com">roussel.geoffrey@gmail.com</A><BR>
Website - <A HREF="http://code.google.com/p/nethooks/">http://code.google.com/p/nethooks/</A></P><BR>
<CENTER>
Permission is hereby granted, free of charge, to any person obtaining a copy of <BR>
this software and associated documentation files (the "Software"), to deal <BR>
in the Software without restriction, including without limitation the rights <BR>
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell <BR>
copies of the Software, and to permit persons to whom the Software is <BR>
furnished to do so, subject to the following conditions:<BR>
<BR>
The above copyright notice and this permission notice shall be included in <BR>
all copies or substantial portions of the Software.<BR>
<BR>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR <BR>
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, <BR>
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE <BR>
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER <BR>
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, <BR>
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN <BR>
THE SOFTWARE.<BR>
</CENTER>
<HR>'
)
	$about_window.setCentralWidget(label)
	screenSize = Qt::Application::desktop().screenGeometry()
	x = screenSize.width * 0.5 - label.geometry.width * 0.5
	y = screenSize.height * 0.5 - label.geometry.height * 0.5
	$about_window.setGeometry(Qt::Rect.new(x, y, $about_window.geometry.width, $about_window.geometry.height))
	$about_window.show()
end

##################################################
# TIMER + THREAD

# Timer automaticly dequeue popups as the user answer them
$t = Qt::Timer.new()
$count = 0
$t.connect(SIGNAL('timeout()')){
	sleep 0.05
	dequeue_popup()
}
$t.setInterval(0)

# TCP listener thread wait for C wrapper data to generate and enqueue new popups
qtClientThread = Thread.new do
	server = TCPServer.open 25252
	puts "Nethooks client: Thread : Listening on port 25252."
	loop {
		client = server.accept
		
		if $FILTERING == true then
			begin
				line = client.gets
				l = line.split("\t")
				if l.size < 11 then
					client.puts "ERROR: 11 elements needed separated with tab.\n"
				else
					#Format:
					#		app = l[0], pid = l[1],
					#		parentApp = l[2], parentPid = l[3],
					#		action = l[4], protocol = l[5], family = l[6],
					#		ip = l[7], port = l[8], dns = l[9],
					#		message = l[10])
					ret = testRuleMatching(app = l[0], action = l[4], ip = l[7], port = l[8], family = l[6], protocol = l[5], strict = false) # set strict to true to disable regexp matching
####					puts "Nethooks client: Thread : TESTING \"#{l[0]}\" action(#{l[4]}) IP(#{l[7]}) port(#{l[8]}) family(#{l[6]}) protocol(#{l[5]})"
					if ret != nil then					# ok we found a rule matching this ----------------- AUTO RULE MATCHING
						appID, ruleID = ret[0], ret[1]
						trusted = $R_TRUST[appID][ruleID] == "1" ? true : false
####						puts "Nethooks client: Thread : FOUND RULE [#{appID}, #{ruleID}] for \"#{l[0]}\" [ PID(#{l[1]}) action(#{l[4]}) IP(#{l[7]}) port(#{l[8]}) family(#{l[6]}) protocol(#{l[5]}) msg(#{l[10].chomp}) ]"
						client.puts trusted # return value to external wrapper
					else			# no rule, enqueue popup and wait for user to answer ----------------- USER ANSWER NEEDED
						queuedID = enqueue_popup(line)
						# wait for user answer
						while($DEQUEUE_ANSWER == nil) do sleep 0.1 end
# DEQUEUE_ANSWER is set when the user click TRUST or BLOCK.
						client.puts $DEQUEUE_ANSWER
# DEQUEUE_ANSWER must be set back to nil.
						$DEQUEUE_ANSWER = nil
					end
				end
			rescue
				puts "Nethooks client: ERROR: Thread : TCP listener : #{$!}"
			end
		else # not filtering
			client.puts true
		end
		client.close
	}
end

$QUEUE_POPUP = []				# 1D enqueued popup list [raw data line] reveived from the C wrapper
$QUEUE_LOCK = false
$DEQUEUE_ANSWER = nil			# user answer when dequeueing popups.
def enqueue_popup(line)
	while($QUEUE_LOCK == true) do sleep 0.1 end
	$QUEUE_LOCK = true
	$QUEUE_POPUP += [line]
	$QUEUE_LOCK = false
	if $DEBUG == 1 then
		puts "Nethooks client: Enqueue popup <<<<"
	end
	return ($QUEUE_POPUP.size - 1)
end

def dequeue_popup()
	if ($popup_window != nil) then return end
	while($QUEUE_LOCK == true) do sleep 0.1 end
	if($QUEUE_POPUP.size == 0) then return end
	while($DEQUEUE_ANSWER != nil) do sleep 0.1 end
	$QUEUE_LOCK = true
	l = $QUEUE_POPUP[0].split("\t")
	if l.size >= 11 then
	
		# we recheck if no new rules match this popup(user might have clicked a previous popup after this one was enqueued...)
		ret = testRuleMatching(app = l[0], action = l[4], ip = l[7], port = l[8], family = l[6], protocol = l[5], strict = false) # set strict to true to disable regexp matching
		puts "Nethooks client: Thread : DEQUEUE TESTING \"#{l[0]}\" action(#{l[4]}) IP(#{l[7]}) port(#{l[8]}) family(#{l[6]}) protocol(#{l[5]})"
		if ret != nil then					# ok we found a rule matching this ----------------- AUTO RULE MATCHING
			appID, ruleID = ret[0], ret[1]
			trusted = $R_TRUST[appID][ruleID] == "1" ? true : false
			puts "Nethooks client: Thread : DEQUEUE FOUND RULE [#{appID}, #{ruleID}] for \"#{l[0]}\" [ PID(#{l[1]}) action(#{l[4]}) IP(#{l[7]}) port(#{l[8]}) family(#{l[6]}) protocol(#{l[5]}) msg(#{l[10].chomp}) ]"

			if trusted == true then
				userTrust()
			else
				userBlock()
			end
			
		else # show popup
		
			popupWindow()
			setPopupData(app = l[0], pid = l[1],
							parentApp = l[2], parentPid = l[3],
							action = l[4], protocol = l[5], family = l[6],
							ip = l[7], port = l[8], dns = l[9],
							message = l[10])
		end
		
	end
	$QUEUE_POPUP.delete_at(0)
	$QUEUE_LOCK = false
	if $DEBUG == 1 then
		puts "Nethooks client: Dequeue popup >>>>"
	end
end

##################################################
# QT application

$a = Qt::Application.new(ARGV)

# Set icon image.
def setIcon(widget, enable = true)
	if not defined? $image_on then
		$image_on = Qt::Image.new("#{UIPath}/UI/nethooks_icon16.png")
		$image_off = Qt::Image.new("#{UIPath}/UI/nethooks_icon16_disable.png")
	end
	begin
		if widget.class == Qt::SystemTrayIcon then # system tray icon
			widget.icon = Qt::Icon.new(Qt::Pixmap::fromImage(enable ? $image_on : $image_off))
		else # or old button widget if no tray available ...TODO enable/disable button or tray
			widget.setWindowIcon(Qt::Icon.new(Qt::Pixmap::fromImage(enable ? $image_on : $image_off)))
			if widget.class == Qt::PushButton then 
				widget.setIcon(Qt::Icon.new(Qt::Pixmap::fromImage(enable ? $image_on : $image_off)))
				widget.setIconSize($image_on.size())
				widget.resize($image_on.size())
			end
		end
	rescue
			puts "Nethooks: cannot set icon for #{widget.inspect}."
	end
end

if not $USE_ICON_TRAY then
	# Desktop icon button
	$buttonIcon = Qt::PushButton.new()
	$buttonIcon.setWindowFlags(Qt::MSWindowsFixedSizeDialogHint | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint)
	setIcon($buttonIcon, $FILTERING)
	$buttonIcon.connect(SIGNAL('clicked()')){
		if $DEBUG == 1 then
			puts "Nethooks client: Icon clicked." 
		end
		if $menu_window == nil then
			menuWindow()
		else
			$menu_window.close
		end
	}
	screenSize = Qt::Application::desktop().screenGeometry()
	xpos, ypos = screenSize.width - (16 + 2), 20
	$buttonIcon.setGeometry(Qt::Rect.new(xpos,ypos,16,16))
else
	$buttonIcon = Qt::SystemTrayIcon.new
	setIcon($buttonIcon, $FILTERING)
	$buttonIcon.connect(SIGNAL('activated(QSystemTrayIcon::ActivationReason)')){ |reason|
		case reason
			when Qt::SystemTrayIcon::Trigger: puts 'Left Click'
				if $DEBUG == 1 then
					puts "Nethooks client: Icon clicked." 
				end
				if $menu_window == nil then
					menuWindow()
				else
					$menu_window.close
					puts "...closing menuWindow"
				end
=begin
			when Qt::SystemTrayIcon::MiddleClick: puts 'Middle Click'
			when Qt::SystemTrayIcon::Context: puts 'Right Click'
			when Qt::SystemTrayIcon::DoubleClick: puts 'Double Click'
=end
		end
	}
	$a.setQuitOnLastWindowClosed(false)
end

$buttonIcon.show() # a pushbutton or a tray icon, see $USE_ICON_TRAY

# Start our timer.
$t.start()
# Load rules.
loadRules()

# QT loop
$a.exec

puts "Nethooks client: Done."
